// Copyright Epic Games, Inc. All Rights Reserved.

#include "vfsservice.h"
#include "vfsimpl.h"

#include <zencore/compactbinarybuilder.h>

namespace zen {

using namespace std::literals;

#if ZEN_WITH_VFS

//////////////////////////////////////////////////////////////////////////

bool
GetContentAsCbObject(HttpServerRequest& HttpReq, CbObject& Cb)
{
	IoBuffer		Payload			   = HttpReq.ReadPayload();
	HttpContentType PayloadContentType = HttpReq.RequestContentType();

	switch (PayloadContentType)
	{
		case HttpContentType::kJSON:
		case HttpContentType::kUnknownContentType:
		case HttpContentType::kText:
			{
				std::string JsonText(reinterpret_cast<const char*>(Payload.GetData()), Payload.GetSize());
				Cb = LoadCompactBinaryFromJson(JsonText).AsObject();
				if (!Cb)
				{
					HttpReq.WriteResponse(HttpResponseCode::BadRequest,
										  HttpContentType::kText,
										  "Content format not supported, expected JSON format");
					return false;
				}
			}
			break;
		case HttpContentType::kCbObject:
			Cb = LoadCompactBinaryObject(Payload);
			if (!Cb)
			{
				HttpReq.WriteResponse(HttpResponseCode::BadRequest,
									  HttpContentType::kText,
									  "Content format not supported, expected compact binary format");
				return false;
			}
			break;
		default:
			HttpReq.WriteResponse(HttpResponseCode::BadRequest, HttpContentType::kText, "Invalid request content type");
			return false;
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////
//
// to test:
//
// echo {"method": "mount", "params": {"path": "d:\\VFS_ROOT"}} | curl.exe http://localhost:8558/vfs --data-binary @-
// echo {"method": "unmount"} | curl.exe http://localhost:8558/vfs --data-binary @-

VfsService::VfsService()
{
	m_Impl = new Impl;

	m_Router.RegisterRoute(
		"info",
		[&](HttpRouterRequest& Request) {
			CbObjectWriter Cbo;
			Cbo << "running" << m_Impl->IsVfsRunning();
			Cbo << "rootpath" << m_Impl->GetMountpointPath();

			Request.ServerRequest().WriteResponse(HttpResponseCode::OK, Cbo.Save());
		},
		HttpVerb::kGet | HttpVerb::kHead);

	m_Router.RegisterRoute(
		"",
		[&](HttpRouterRequest& Req) {
			CbObject Payload;

			if (!GetContentAsCbObject(Req.ServerRequest(), Payload))
				return;

			std::string_view RpcName = Payload["method"sv].AsString();

			if (RpcName == "mount"sv)
			{
				CbObjectView	 Params	   = Payload["params"sv].AsObjectView();
				std::string_view Mountpath = Params["path"sv].AsString();

				if (Mountpath.empty())
				{
					return Req.ServerRequest().WriteResponse(HttpResponseCode::BadRequest, HttpContentType::kText, "no path specified");
				}

				if (m_Impl->IsVfsRunning())
				{
					return Req.ServerRequest().WriteResponse(HttpResponseCode::BadRequest, HttpContentType::kText, "VFS already mounted");
				}

				try
				{
					m_Impl->Mount(Mountpath);
				}
				catch (const std::exception& Ex)
				{
					return Req.ServerRequest().WriteResponse(HttpResponseCode::BadRequest, HttpContentType::kText, Ex.what());
				}

				Req.ServerRequest().WriteResponse(HttpResponseCode::OK);
			}
			else if (RpcName == "unmount"sv)
			{
				if (!m_Impl->IsVfsRunning())
				{
					return Req.ServerRequest().WriteResponse(HttpResponseCode::BadRequest, HttpContentType::kText, "VFS not active");
				}

				try
				{
					m_Impl->Unmount();
				}
				catch (const std::exception& Ex)
				{
					return Req.ServerRequest().WriteResponse(HttpResponseCode::BadRequest, HttpContentType::kText, Ex.what());
				}

				Req.ServerRequest().WriteResponse(HttpResponseCode::OK);
			}
			else
			{
				Req.ServerRequest().WriteResponse(HttpResponseCode::BadRequest, HttpContentType::kText, "unknown RPC"sv);
			}
		},
		HttpVerb::kPost);
}

VfsService::~VfsService()
{
	delete m_Impl;
}

void
VfsService::Mount(std::string_view MountPoint)
{
	m_Impl->Mount(MountPoint);
}

void
VfsService::Unmount()
{
	m_Impl->Unmount();
}

void
VfsService::AddService(Ref<ProjectStore>&& Ps)
{
	m_Impl->AddService(std::move(Ps));
}

void
VfsService::AddService(Ref<ZenCacheStore>&& Z$)
{
	m_Impl->AddService(std::move(Z$));
}

#else

VfsService::VfsService()
{
}

VfsService::~VfsService()
{
}

void
VfsService::Mount(std::string_view MountPoint)
{
	ZEN_UNUSED(MountPoint);
}

void
VfsService::Unmount()
{
}

void
VfsService::AddService(Ref<ProjectStore>&& Ps)
{
	ZEN_UNUSED(Ps);
}

void
VfsService::AddService(Ref<ZenCacheStore>&& Z$)
{
	ZEN_UNUSED(Z$);
}

#endif

const char*
VfsService::BaseUri() const
{
	return "/vfs/";
}

void
VfsService::HandleRequest(HttpServerRequest& HttpServiceRequest)
{
	m_Router.HandleRequest(HttpServiceRequest);
}

}  // namespace zen
